Skip to content

Commit 1930360

Browse files
committed
General overview on content type handlers
1 parent a96f9dd commit 1930360

2 files changed

Lines changed: 121 additions & 0 deletions

File tree

docs/devs/handlers/index.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Handlers
2+
3+
XenForo uses a "handler" pattern to allow different content types to plug into shared systems. Rather than having a single monolithic class that knows about every type of content, each system defines an abstract handler class, and individual content types provide their own concrete implementation.
4+
5+
For example, the attachment system doesn't need to know the specifics of how a post or a conversation message works. Instead, each content type provides an attachment handler that tells the system how to check permissions, manage constraints, and clean up when attachments are deleted.
6+
7+
## How handlers work
8+
9+
The handler pattern in XenForo follows a consistent structure:
10+
11+
1. **An abstract base class** defines the contract, including the methods every handler must implement and any optional hooks.
12+
2. **Concrete handler classes** extend the abstract class for each content type that supports the feature.
13+
3. **A content type field** registers the handler class against a content type, so the system can discover and instantiate it at runtime.
14+
15+
## Content type fields
16+
17+
At the heart of the handler system is the `xf_content_type_field` table. This table maps content types (like `post`, `thread`, `profile_post`) to handler classes for each system they participate in.
18+
19+
Each row has three columns:
20+
21+
| Column | Description |
22+
|---|---|
23+
| `content_type` | The content type identifier (e.g. `post`) |
24+
| `field_name` | The system this handler belongs to (e.g. `attachment_handler_class`) |
25+
| `field_value` | The fully qualified class name of the handler |
26+
27+
For example, the post content type registers its attachment handler like this:
28+
29+
| content_type | field_name | field_value |
30+
|---|---|---|
31+
| `post` | `attachment_handler_class` | `XF\Attachment\PostHandler` |
32+
33+
A single content type can register handlers for many different systems. The `post` content type, for instance, has handler registrations for attachments, alerts, reactions, reports, bookmarks, tags, and more.
34+
35+
### Registering a handler
36+
37+
Content type fields are managed through the Admin control panel at **Development > Execution manipulation > Content types**. This area is only available when [development mode](../development-tools.md#enabling-development-mode) is enabled.
38+
39+
From here you can add a new content type field by specifying the content type, field name, and the fully qualified class name of the handler. You will also need to associate the content type field with your add-on.
40+
41+
When your add-on's development output is exported, the content type field will be written to a JSON file in your add-on's `_output/content_type_fields/` directory following the naming convention `{content_type}-{field_name}.json`. For example:
42+
43+
```json title="src/addons/Demo/Portal/_output/content_type_fields/demo_item-attachment_handler_class.json"
44+
{
45+
"content_type": "demo_item",
46+
"field_name": "attachment_handler_class",
47+
"field_value": "Demo\\Portal\\Attachment\\ItemHandler"
48+
}
49+
```
50+
51+
:::note
52+
Your entity structure must also declare the matching `contentType` for the content type system to associate it correctly:
53+
54+
```php
55+
$structure->contentType = 'demo_item';
56+
```
57+
58+
See [Entities, finders and repositories](../entities-finders-repositories.md#content-type) for more details.
59+
:::
60+
61+
### How handlers are discovered
62+
63+
At runtime, the content type fields are cached in the data registry. You can look up all handlers for a given system or retrieve a specific handler for a content type:
64+
65+
```php
66+
// Get all attachment handler classes, keyed by content type
67+
$handlers = \XF::app()->getContentTypeField('attachment_handler_class');
68+
// Returns: ['post' => 'XF\Attachment\PostHandler', 'conversation_message' => 'XF\Attachment\ConversationMessageHandler', ...]
69+
70+
// Get a single handler class for a specific content type
71+
$handlerClass = \XF::app()->getContentTypeFieldValue('post', 'attachment_handler_class');
72+
// Returns: 'XF\Attachment\PostHandler'
73+
```
74+
75+
Each system typically has a repository method that handles instantiation. The general pattern looks like this:
76+
77+
```php
78+
public function getAttachmentHandler(string $type): ?AbstractHandler
79+
{
80+
$handlerClass = \XF::app()->getContentTypeFieldValue($type, 'attachment_handler_class');
81+
if (!$handlerClass)
82+
{
83+
return null;
84+
}
85+
86+
$handlerClass = \XF::extendClass($handlerClass);
87+
return new $handlerClass($type);
88+
}
89+
```
90+
91+
Note the use of `\XF::extendClass()`. This ensures that any class extensions applied by other add-ons are respected when the handler is instantiated.
92+
93+
## Common handler systems
94+
95+
The following is a list of the handler systems available in XenForo, along with their content type field name and abstract base class:
96+
97+
| System | Field name | Base class |
98+
|---|---|---|
99+
| Activity log | `activity_log_handler_class` | `XF\ActivityLog\AbstractHandler` |
100+
| Alerts | `alert_handler_class` | `XF\Alert\AbstractHandler` |
101+
| Approval queue | `approval_queue_handler_class` | `XF\ApprovalQueue\AbstractHandler` |
102+
| Attachments | `attachment_handler_class` | `XF\Attachment\AbstractHandler` |
103+
| Bookmarks | `bookmark_handler_class` | `XF\Bookmark\AbstractHandler` |
104+
| Change log | `change_log_handler_class` | `XF\ChangeLog\AbstractHandler` |
105+
| Content voting | `content_vote_handler_class` | `XF\ContentVote\AbstractHandler` |
106+
| Edit history | `edit_history_handler_class` | `XF\EditHistory\AbstractHandler` |
107+
| Featured content | `featured_content_handler_class` | `XF\FeaturedContent\AbstractHandler` |
108+
| Find new | `find_new_handler_class` | `XF\FindNew\AbstractHandler` |
109+
| Inline moderation | `inline_mod_handler_class` | `XF\InlineMod\AbstractHandler` |
110+
| Moderator log | `moderator_log_handler_class` | `XF\ModeratorLog\AbstractHandler` |
111+
| News feed | `news_feed_handler_class` | `XF\NewsFeed\AbstractHandler` |
112+
| Polls | `poll_handler_class` | `XF\Poll\AbstractHandler` |
113+
| Reactions | `reaction_handler_class` | `XF\Reaction\AbstractHandler` |
114+
| Reports | `report_handler_class` | `XF\Report\AbstractHandler` |
115+
| Sitemap | `sitemap_handler_class` | `XF\Sitemap\AbstractHandler` |
116+
| Stats | `stats_handler_class` | `XF\Stats\AbstractHandler` |
117+
| Tags | `tag_handler_class` | `XF\Tag\AbstractHandler` |
118+
| Trending content | `trending_content_handler_class` | `XF\TrendingContent\AbstractHandler` |
119+
| Warnings | `warning_handler_class` | `XF\Warning\AbstractHandler` |
120+
| Webhook events | `webhook_handler_class` | `XF\Webhook\Event\AbstractHandler` |

sidebars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ const sidebars: SidebarsConfig = {
329329
'devs/entities-finders-repositories',
330330
'devs/criteria',
331331
'devs/managing-the-schema',
332+
'devs/handlers/index',
332333
'devs/lets-build-an-add-on',
333334
{
334335
type: 'category',

0 commit comments

Comments
 (0)