Initial commit
This commit is contained in:
11
.claude-plugin/plugin.json
Normal file
11
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "drupal-backend",
|
||||
"description": "Drupal Back End Specialist skill for custom module development, hooks, APIs, and PHP programming (Drupal 8-11+). Use when building custom modules, implementing hooks, working with entities, forms, plugins, or services.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Drupal Skills Team"
|
||||
},
|
||||
"skills": [
|
||||
"./"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# drupal-backend
|
||||
|
||||
Drupal Back End Specialist skill for custom module development, hooks, APIs, and PHP programming (Drupal 8-11+). Use when building custom modules, implementing hooks, working with entities, forms, plugins, or services.
|
||||
463
SKILL.md
Normal file
463
SKILL.md
Normal file
@@ -0,0 +1,463 @@
|
||||
---
|
||||
name: drupal-backend
|
||||
description: Drupal Back End Specialist skill for custom module development, hooks, APIs, and PHP programming (Drupal 8-11+). Use when building custom modules, implementing hooks, working with entities, forms, plugins, or services.
|
||||
---
|
||||
|
||||
# Drupal Back End Development
|
||||
|
||||
## Overview
|
||||
|
||||
Enable expert-level Drupal back end development capabilities. Provide comprehensive guidance for custom module development, PHP programming, API usage, hooks, plugins, services, and database operations for Drupal 8, 9, 10, and 11+.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Invoke this skill when working with:
|
||||
- **Custom module development**: Creating new Drupal modules
|
||||
- **Hooks**: Implementing Drupal hooks to alter system behavior
|
||||
- **Controllers & routing**: Building custom pages and routes
|
||||
- **Forms**: Creating configuration or custom forms
|
||||
- **Entities**: Working with content entities or config entities
|
||||
- **Plugins**: Building blocks, field types, formatters, or other plugins
|
||||
- **Services**: Creating reusable business logic services
|
||||
- **Database operations**: Custom queries and schema definitions
|
||||
- **APIs**: Entity API, Form API, Database API, Plugin API
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### 1. Custom Module Development
|
||||
|
||||
Create complete, standards-compliant Drupal modules:
|
||||
|
||||
**Quick start workflow:**
|
||||
1. Use module template from `assets/module-template/`
|
||||
2. Replace `MODULENAME` with machine name (lowercase, underscores)
|
||||
3. Replace `MODULELABEL` with human-readable name
|
||||
4. Implement functionality using Drupal APIs
|
||||
5. Enable module and test: `ddev drush en mymodule -y`
|
||||
|
||||
**Module structure:**
|
||||
- `.info.yml` - Module metadata and dependencies
|
||||
- `.module` - Hook implementations
|
||||
- `.routing.yml` - Route definitions
|
||||
- `.services.yml` - Service definitions
|
||||
- `.permissions.yml` - Custom permissions
|
||||
- `src/` - PHP classes (PSR-4 autoloading)
|
||||
- `config/` - Configuration files
|
||||
|
||||
**Reference documentation:**
|
||||
- `references/module_structure.md` - Complete module patterns
|
||||
- `references/hooks.md` - Hook implementations
|
||||
|
||||
### 2. Hooks System
|
||||
|
||||
Implement hooks to alter Drupal behavior:
|
||||
|
||||
**Common hooks:**
|
||||
- `hook_entity_presave()` - Modify entities before saving
|
||||
- `hook_entity_insert/update/delete()` - React to entity changes
|
||||
- `hook_form_alter()` - Modify any form
|
||||
- `hook_node_access()` - Control node access
|
||||
- `hook_cron()` - Perform periodic tasks
|
||||
- `hook_install/uninstall()` - Module installation tasks
|
||||
|
||||
**Hook pattern:**
|
||||
```php
|
||||
function MODULENAME_hook_name($param1, &$param2) {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
**Best practices:**
|
||||
- Implement hooks in `.module` file only
|
||||
- Use proper type hints
|
||||
- Document with PHPDoc blocks
|
||||
- Check for entity types before processing
|
||||
- Be mindful of performance
|
||||
|
||||
### 3. Controllers & Routing
|
||||
|
||||
Create custom pages and endpoints:
|
||||
|
||||
**Route definition** (`.routing.yml`):
|
||||
```yaml
|
||||
mymodule.example:
|
||||
path: '/example/{param}'
|
||||
defaults:
|
||||
_controller: '\Drupal\mymodule\Controller\ExampleController::content'
|
||||
_title: 'Example Page'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
param: \d+
|
||||
```
|
||||
|
||||
**Controller pattern:**
|
||||
```php
|
||||
namespace Drupal\mymodule\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
|
||||
class ExampleController extends ControllerBase {
|
||||
public function content() {
|
||||
return ['#markup' => $this->t('Hello!')];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**With dependency injection:**
|
||||
```php
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity_type.manager'));
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Forms API
|
||||
|
||||
Build configuration and custom forms:
|
||||
|
||||
**Configuration form:**
|
||||
```php
|
||||
class SettingsForm extends ConfigFormBase {
|
||||
protected function getEditableConfigNames() {
|
||||
return ['mymodule.settings'];
|
||||
}
|
||||
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config('mymodule.settings');
|
||||
|
||||
$form['api_key'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('API Key'),
|
||||
'#default_value' => $config->get('api_key'),
|
||||
];
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->config('mymodule.settings')
|
||||
->set('api_key', $form_state->getValue('api_key'))
|
||||
->save();
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Form validation:**
|
||||
```php
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
if (strlen($form_state->getValue('field')) < 5) {
|
||||
$form_state->setErrorByName('field', $this->t('Too short.'));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Entity API
|
||||
|
||||
Work with content and configuration entities:
|
||||
|
||||
**Loading entities:**
|
||||
```php
|
||||
// Load single entity
|
||||
$node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
|
||||
|
||||
// Load multiple entities
|
||||
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple([1, 2, 3]);
|
||||
|
||||
// Load by properties
|
||||
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadByProperties([
|
||||
'type' => 'article',
|
||||
'status' => 1,
|
||||
]);
|
||||
```
|
||||
|
||||
**Creating/saving entities:**
|
||||
```php
|
||||
$node = \Drupal::entityTypeManager()->getStorage('node')->create([
|
||||
'type' => 'article',
|
||||
'title' => 'My Article',
|
||||
'body' => ['value' => 'Content', 'format' => 'basic_html'],
|
||||
]);
|
||||
$node->save();
|
||||
```
|
||||
|
||||
**Entity queries:**
|
||||
```php
|
||||
$query = \Drupal::entityQuery('node')
|
||||
->condition('type', 'article')
|
||||
->condition('status', 1)
|
||||
->accessCheck(TRUE)
|
||||
->sort('created', 'DESC')
|
||||
->range(0, 10);
|
||||
$nids = $query->execute();
|
||||
```
|
||||
|
||||
### 6. Plugin System
|
||||
|
||||
Create custom plugins (blocks, fields, etc.):
|
||||
|
||||
**Block plugin:**
|
||||
```php
|
||||
/**
|
||||
* @Block(
|
||||
* id = "mymodule_custom_block",
|
||||
* admin_label = @Translation("Custom Block"),
|
||||
* )
|
||||
*/
|
||||
class CustomBlock extends BlockBase {
|
||||
public function build() {
|
||||
return ['#markup' => 'Block content'];
|
||||
}
|
||||
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$form['setting'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Setting'),
|
||||
'#default_value' => $this->configuration['setting'] ?? '',
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['setting'] = $form_state->getValue('setting');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Services & Dependency Injection
|
||||
|
||||
Create reusable services:
|
||||
|
||||
**Service definition** (`.services.yml`):
|
||||
```yaml
|
||||
services:
|
||||
mymodule.custom_service:
|
||||
class: Drupal\mymodule\Service\CustomService
|
||||
arguments: ['@entity_type.manager', '@current_user']
|
||||
```
|
||||
|
||||
**Service class:**
|
||||
```php
|
||||
namespace Drupal\mymodule\Service;
|
||||
|
||||
class CustomService {
|
||||
protected $entityTypeManager;
|
||||
protected $currentUser;
|
||||
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, AccountProxyInterface $current_user) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
public function doSomething() {
|
||||
// Business logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Database Operations
|
||||
|
||||
Execute custom queries:
|
||||
|
||||
**Using Database API:**
|
||||
```php
|
||||
$database = \Drupal::database();
|
||||
|
||||
// Select query
|
||||
$query = $database->select('node_field_data', 'n')
|
||||
->fields('n', ['nid', 'title'])
|
||||
->condition('type', 'article')
|
||||
->condition('status', 1)
|
||||
->range(0, 10);
|
||||
$results = $query->execute()->fetchAll();
|
||||
|
||||
// Insert
|
||||
$database->insert('mymodule_table')
|
||||
->fields(['name' => 'Example', 'value' => 123])
|
||||
->execute();
|
||||
|
||||
// Update
|
||||
$database->update('mymodule_table')
|
||||
->fields(['value' => 456])
|
||||
->condition('name', 'Example')
|
||||
->execute();
|
||||
```
|
||||
|
||||
**Schema definition** (`.install` file):
|
||||
```php
|
||||
function mymodule_schema() {
|
||||
$schema['mymodule_table'] = [
|
||||
'fields' => [
|
||||
'id' => ['type' => 'serial', 'not null' => TRUE],
|
||||
'name' => ['type' => 'varchar', 'length' => 255, 'not null' => TRUE],
|
||||
'value' => ['type' => 'int', 'not null' => TRUE, 'default' => 0],
|
||||
],
|
||||
'primary key' => ['id'],
|
||||
'indexes' => ['name' => ['name']],
|
||||
];
|
||||
return $schema;
|
||||
}
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Creating a Custom Module
|
||||
|
||||
1. **Scaffold the module:**
|
||||
```bash
|
||||
cp -r assets/module-template/ /path/to/drupal/modules/custom/mymodule/
|
||||
cd /path/to/drupal/modules/custom/mymodule/
|
||||
mv MODULENAME.info.yml mymodule.info.yml
|
||||
mv MODULENAME.module mymodule.module
|
||||
mv MODULENAME.routing.yml mymodule.routing.yml
|
||||
```
|
||||
|
||||
2. **Update module files:**
|
||||
- Replace `MODULENAME` with machine name
|
||||
- Replace `MODULELABEL` with readable name
|
||||
- Update `.info.yml` dependencies
|
||||
- Customize controller, routes, logic
|
||||
|
||||
3. **Enable and test:**
|
||||
```bash
|
||||
ddev drush en mymodule -y
|
||||
ddev drush cr
|
||||
```
|
||||
|
||||
4. **Develop iteratively:**
|
||||
- Implement functionality
|
||||
- Clear cache: `ddev drush cr`
|
||||
- Test thoroughly
|
||||
- Check logs: `ddev drush watchdog:tail`
|
||||
|
||||
### Standard Development Workflow
|
||||
|
||||
1. **Plan**: Identify what APIs/hooks you need
|
||||
2. **Code**: Implement using Drupal APIs
|
||||
3. **Test**: Enable module, clear cache, test functionality
|
||||
4. **Debug**: Check logs, use Xdebug if needed
|
||||
5. **Refine**: Optimize, add tests, improve code quality
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Module Development
|
||||
1. **PSR-4 autoloading**: Follow namespace conventions
|
||||
2. **Dependency injection**: Use DI in classes
|
||||
3. **Hooks**: Implement only in `.module` files
|
||||
4. **Type hints**: Use proper PHP type declarations
|
||||
5. **Documentation**: Add PHPDoc blocks
|
||||
6. **Testing**: Write automated tests
|
||||
|
||||
### Code Quality
|
||||
1. **Drupal coding standards**: Follow PHPCS rules
|
||||
2. **Security**: Validate input, sanitize output, check permissions
|
||||
3. **Performance**: Cache when possible, optimize queries
|
||||
4. **Accessibility**: Ensure forms and pages are accessible
|
||||
5. **Internationalization**: Use `$this->t()` for all strings
|
||||
|
||||
### API Usage
|
||||
1. **Entity API**: Prefer entity operations over direct DB queries
|
||||
2. **Configuration**: Use Config API for settings
|
||||
3. **State API**: Use for temporary/non-exportable data
|
||||
4. **Cache API**: Implement caching for expensive operations
|
||||
5. **Queue API**: Use for long-running tasks
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Event Subscriber
|
||||
|
||||
```php
|
||||
class MyModuleSubscriber implements EventSubscriberInterface {
|
||||
public static function getSubscribedEvents() {
|
||||
return [KernelEvents::REQUEST => ['onRequest', 0]];
|
||||
}
|
||||
|
||||
public function onRequest(RequestEvent $event) {
|
||||
// Handle event
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Permission
|
||||
|
||||
```yaml
|
||||
# mymodule.permissions.yml
|
||||
administer mymodule:
|
||||
title: 'Administer My Module'
|
||||
restrict access: true
|
||||
```
|
||||
|
||||
### Custom Access Check
|
||||
|
||||
```php
|
||||
class CustomAccessCheck implements AccessInterface {
|
||||
public function access(AccountInterface $account) {
|
||||
return AccessResult::allowedIf($account->hasPermission('access content'));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Module Not Appearing
|
||||
- Check `.info.yml` syntax
|
||||
- Verify `core_version_requirement`
|
||||
- Clear cache: `ddev drush cr`
|
||||
|
||||
### Hooks Not Working
|
||||
- Verify hook name is correct
|
||||
- Clear cache after adding hooks
|
||||
- Check module weight if hook order matters
|
||||
|
||||
### Class Not Found
|
||||
- Verify PSR-4 namespace matches directory
|
||||
- Clear cache to rebuild class registry
|
||||
- Check autoloading with `composer dump-autoload`
|
||||
|
||||
### Database Errors
|
||||
- Check schema definition syntax
|
||||
- Run `ddev drush updb` after schema changes
|
||||
- Verify table/column names in queries
|
||||
|
||||
## Resources
|
||||
|
||||
### Reference Documentation
|
||||
|
||||
- **`references/hooks.md`** - Common hooks with examples
|
||||
- Entity hooks
|
||||
- Form hooks
|
||||
- Node hooks
|
||||
- Installation hooks
|
||||
- Token hooks
|
||||
|
||||
- **`references/module_structure.md`** - Module patterns
|
||||
- Controllers and routing
|
||||
- Forms (config and custom)
|
||||
- Plugins (blocks, fields, etc.)
|
||||
- Services and DI
|
||||
- Event subscribers
|
||||
|
||||
### Asset Templates
|
||||
|
||||
- **`assets/module-template/`** - Module scaffold
|
||||
- `.info.yml` metadata
|
||||
- `.module` hook file
|
||||
- `.routing.yml` routes
|
||||
- Example controller
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
### Drupal 8 vs 9 vs 10 vs 11
|
||||
- **Core APIs**: Largely consistent across versions
|
||||
- **PHP requirements**: 8.x (7.0+), 9.x (7.3+), 10.x (8.1+), 11.x (8.3+)
|
||||
- **Deprecations**: Check change records when upgrading
|
||||
- **Symfony**: Different Symfony versions in each Drupal version
|
||||
|
||||
## See Also
|
||||
|
||||
- **drupal-frontend** - Theme development, Twig templates
|
||||
- **drupal-tooling** - DDEV and Drush development tools
|
||||
- [Drupal API](https://api.drupal.org/) - Official API documentation
|
||||
10
assets/module-template/MODULENAME.info.yml
Normal file
10
assets/module-template/MODULENAME.info.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
name: MODULELABEL
|
||||
description: 'Description of what this module does.'
|
||||
type: module
|
||||
core_version_requirement: ^9 || ^10 || ^11
|
||||
package: Custom
|
||||
|
||||
# Add dependencies as needed
|
||||
# dependencies:
|
||||
# - drupal:node
|
||||
# - drupal:views
|
||||
22
assets/module-template/MODULENAME.module
Normal file
22
assets/module-template/MODULENAME.module
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Primary module hooks for MODULELABEL module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function MODULENAME_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.MODULENAME':
|
||||
$output = '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('Description of what this module does.') . '</p>';
|
||||
return $output;
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
8
assets/module-template/MODULENAME.routing.yml
Normal file
8
assets/module-template/MODULENAME.routing.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
# Example route - customize or remove as needed
|
||||
MODULENAME.example:
|
||||
path: '/MODULENAME/example'
|
||||
defaults:
|
||||
_controller: '\Drupal\MODULENAME\Controller\ExampleController::content'
|
||||
_title: 'Example Page'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
36
assets/module-template/README.md
Normal file
36
assets/module-template/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Module Template
|
||||
|
||||
This is a basic Drupal module template. To use it:
|
||||
|
||||
1. Copy this directory to your Drupal installation: `modules/custom/yourmodulename/`
|
||||
2. Rename files from `MODULENAME.*` to `yourmodulename.*`
|
||||
3. Replace all instances of `MODULENAME` with your module's machine name (lowercase, underscores)
|
||||
4. Replace all instances of `MODULELABEL` with your module's human-readable name
|
||||
5. Customize the module as needed
|
||||
|
||||
## Files Included
|
||||
|
||||
- `MODULENAME.info.yml` - Module metadata
|
||||
- `MODULENAME.module` - Hook implementations
|
||||
- `MODULENAME.routing.yml` - Route definitions
|
||||
- `src/Controller/ExampleController.php` - Example controller
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
MODULENAME/
|
||||
├── MODULENAME.info.yml
|
||||
├── MODULENAME.module
|
||||
├── MODULENAME.routing.yml
|
||||
└── src/
|
||||
└── Controller/
|
||||
└── ExampleController.php
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
After creating your module:
|
||||
|
||||
1. Enable it: `ddev drush en MODULENAME -y`
|
||||
2. Clear cache: `ddev drush cr`
|
||||
3. Visit your route: `/MODULENAME/example`
|
||||
24
assets/module-template/src/Controller/ExampleController.php
Normal file
24
assets/module-template/src/Controller/ExampleController.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\MODULENAME\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
|
||||
/**
|
||||
* Returns responses for MODULELABEL routes.
|
||||
*/
|
||||
class ExampleController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Builds the response.
|
||||
*/
|
||||
public function content() {
|
||||
$build['content'] = [
|
||||
'#type' => 'markup',
|
||||
'#markup' => $this->t('Example content from MODULELABEL module.'),
|
||||
];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
73
plugin.lock.json
Normal file
73
plugin.lock.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:Omedia/drupal-skill:drupal-backend",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "b01bb92100a1bbf835c2f0d5f39c22e63a251b15",
|
||||
"treeHash": "c235a901bfcd23213701827c79c058950e9c48d883d8fb08c32cd16b0df8f03a",
|
||||
"generatedAt": "2025-11-28T10:12:18.661697Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "drupal-backend",
|
||||
"description": "Drupal Back End Specialist skill for custom module development, hooks, APIs, and PHP programming (Drupal 8-11+). Use when building custom modules, implementing hooks, working with entities, forms, plugins, or services.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "39d7bfaf81af60cb1d5796563ec3b536b6cc0285b315898bd807729e038926a2"
|
||||
},
|
||||
{
|
||||
"path": "SKILL.md",
|
||||
"sha256": "ba66654626364a38102b57081430e32cbe24aae3c1077ba99571fed69986320d"
|
||||
},
|
||||
{
|
||||
"path": "references/hooks.md",
|
||||
"sha256": "63e141b1304b14bd2bda9610947ca656727bf5b111bc4bd7f1e6521f179e4dd2"
|
||||
},
|
||||
{
|
||||
"path": "references/module_structure.md",
|
||||
"sha256": "59600391e6db8eb6f08df3e40ea7283d8d152b1ba96da12be9dc95268211e61e"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "68d5411055dbf2de70c937437e0ae48b33914a1452e97c76e323b5170459e899"
|
||||
},
|
||||
{
|
||||
"path": "assets/module-template/MODULENAME.info.yml",
|
||||
"sha256": "1e3ebd21fee61351c7a68f251fc340531668a040692ab8d24d84d6b858b8e823"
|
||||
},
|
||||
{
|
||||
"path": "assets/module-template/README.md",
|
||||
"sha256": "10c0a33f9c0fd6fbed2c8c0ca7446a18d12e0419c2d0e6e89e0bae3028942c69"
|
||||
},
|
||||
{
|
||||
"path": "assets/module-template/MODULENAME.routing.yml",
|
||||
"sha256": "a8d354e01c6741f5ace2944a8d6c053138996cbd53c84a3e466b7243e02122d9"
|
||||
},
|
||||
{
|
||||
"path": "assets/module-template/MODULENAME.module",
|
||||
"sha256": "70b84fe9bb883b271543e35fcf131fe95e6ce480060b6606d0135d418025f354"
|
||||
},
|
||||
{
|
||||
"path": "assets/module-template/src/Controller/ExampleController.php",
|
||||
"sha256": "0e5053193ec219b8160dcdcac04c9182021962d313f0280f22da011ab560893f"
|
||||
}
|
||||
],
|
||||
"dirSha256": "c235a901bfcd23213701827c79c058950e9c48d883d8fb08c32cd16b0df8f03a"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
317
references/hooks.md
Normal file
317
references/hooks.md
Normal file
@@ -0,0 +1,317 @@
|
||||
# Common Drupal Hooks Reference
|
||||
|
||||
This reference provides commonly used Drupal hooks across versions 8-11+.
|
||||
|
||||
## Entity Hooks
|
||||
|
||||
### hook_entity_presave()
|
||||
Called before an entity is saved.
|
||||
```php
|
||||
function mymodule_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
|
||||
if ($entity->getEntityTypeId() == 'node') {
|
||||
// Modify entity before saving
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### hook_entity_insert()
|
||||
Called after a new entity is created.
|
||||
```php
|
||||
function mymodule_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// React to new entity creation
|
||||
}
|
||||
```
|
||||
|
||||
### hook_entity_update()
|
||||
Called after an existing entity is updated.
|
||||
```php
|
||||
function mymodule_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// React to entity updates
|
||||
}
|
||||
```
|
||||
|
||||
### hook_entity_delete()
|
||||
Called after an entity is deleted.
|
||||
```php
|
||||
function mymodule_entity_delete(Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// Clean up related data
|
||||
}
|
||||
```
|
||||
|
||||
### hook_entity_view()
|
||||
Alter entity display.
|
||||
```php
|
||||
function mymodule_entity_view(array &$build, Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode) {
|
||||
if ($entity->getEntityTypeId() == 'node') {
|
||||
$build['#attached']['library'][] = 'mymodule/custom-library';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### hook_entity_view_alter()
|
||||
Alter entity view arrays after all modules have added their implementations.
|
||||
```php
|
||||
function mymodule_entity_view_alter(array &$build, Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
|
||||
// Modify the render array
|
||||
}
|
||||
```
|
||||
|
||||
## Form Hooks
|
||||
|
||||
### hook_form_alter()
|
||||
Modify any form.
|
||||
```php
|
||||
function mymodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
|
||||
if ($form_id == 'node_article_form') {
|
||||
$form['title']['#description'] = 'Custom description';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### hook_form_FORM_ID_alter()
|
||||
Modify a specific form by form ID.
|
||||
```php
|
||||
function mymodule_form_node_article_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
|
||||
$form['actions']['submit']['#value'] = t('Save Article');
|
||||
}
|
||||
```
|
||||
|
||||
## Node Hooks
|
||||
|
||||
### hook_node_presave()
|
||||
Act before a node is saved.
|
||||
```php
|
||||
function mymodule_node_presave(Drupal\node\NodeInterface $node) {
|
||||
// Modify node before saving
|
||||
$node->setTitle('Prefix: ' . $node->getTitle());
|
||||
}
|
||||
```
|
||||
|
||||
### hook_node_insert()
|
||||
Respond to node creation.
|
||||
```php
|
||||
function mymodule_node_insert(Drupal\node\NodeInterface $node) {
|
||||
// Log or notify on new node
|
||||
}
|
||||
```
|
||||
|
||||
### hook_node_update()
|
||||
Respond to node updates.
|
||||
```php
|
||||
function mymodule_node_update(Drupal\node\NodeInterface $node) {
|
||||
// React to node changes
|
||||
}
|
||||
```
|
||||
|
||||
### hook_node_delete()
|
||||
Respond to node deletion.
|
||||
```php
|
||||
function mymodule_node_delete(Drupal\node\NodeInterface $node) {
|
||||
// Clean up related data
|
||||
}
|
||||
```
|
||||
|
||||
### hook_node_access()
|
||||
Control access to nodes.
|
||||
```php
|
||||
function mymodule_node_access(\Drupal\node\NodeInterface $node, $op, \Drupal\Core\Session\AccountInterface $account) {
|
||||
// Return AccessResult::allowed(), AccessResult::forbidden(), or AccessResult::neutral()
|
||||
}
|
||||
```
|
||||
|
||||
## Page & Theme Hooks
|
||||
|
||||
### hook_preprocess_HOOK()
|
||||
Preprocess variables for templates.
|
||||
```php
|
||||
function mymodule_preprocess_page(&$variables) {
|
||||
$variables['custom_var'] = 'Custom value';
|
||||
}
|
||||
|
||||
function mymodule_preprocess_node(&$variables) {
|
||||
$node = $variables['node'];
|
||||
$variables['custom_date'] = $node->getCreatedTime();
|
||||
}
|
||||
```
|
||||
|
||||
### hook_theme()
|
||||
Register theme implementations.
|
||||
```php
|
||||
function mymodule_theme($existing, $type, $theme, $path) {
|
||||
return [
|
||||
'mymodule_custom_template' => [
|
||||
'variables' => [
|
||||
'title' => NULL,
|
||||
'content' => NULL,
|
||||
],
|
||||
'template' => 'mymodule-custom-template',
|
||||
],
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### hook_page_attachments()
|
||||
Add attachments (CSS, JS, metatags) to a page.
|
||||
```php
|
||||
function mymodule_page_attachments(array &$attachments) {
|
||||
$attachments['#attached']['library'][] = 'mymodule/global-styling';
|
||||
$attachments['#attached']['drupalSettings']['mymodule']['setting'] = 'value';
|
||||
}
|
||||
```
|
||||
|
||||
### hook_theme_suggestions_HOOK()
|
||||
Provide theme suggestions.
|
||||
```php
|
||||
function mymodule_theme_suggestions_page_alter(array &$suggestions, array $variables) {
|
||||
if ($node = \Drupal::routeMatch()->getParameter('node')) {
|
||||
$suggestions[] = 'page__node__' . $node->bundle();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Menu & Routing Hooks
|
||||
|
||||
### hook_menu_links_discovered_alter()
|
||||
Alter menu links.
|
||||
```php
|
||||
function mymodule_menu_links_discovered_alter(&$links) {
|
||||
// Modify menu links
|
||||
}
|
||||
```
|
||||
|
||||
## Block Hooks
|
||||
|
||||
### hook_block_access()
|
||||
Control access to blocks.
|
||||
```php
|
||||
function mymodule_block_access(\Drupal\block\Entity\Block $block, $operation, \Drupal\Core\Session\AccountInterface $account) {
|
||||
// Return AccessResult
|
||||
}
|
||||
```
|
||||
|
||||
### hook_block_view_alter()
|
||||
Alter block content.
|
||||
```php
|
||||
function mymodule_block_view_alter(array &$build, \Drupal\Core\Block\BlockPluginInterface $block) {
|
||||
if ($build['#plugin_id'] == 'system_branding_block') {
|
||||
$build['#pre_render'][] = 'mymodule_prerender_branding';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## User Hooks
|
||||
|
||||
### hook_user_login()
|
||||
Act when user logs in.
|
||||
```php
|
||||
function mymodule_user_login($account) {
|
||||
// Log user login, set message, etc.
|
||||
}
|
||||
```
|
||||
|
||||
### hook_user_logout()
|
||||
Act when user logs out.
|
||||
```php
|
||||
function mymodule_user_logout($account) {
|
||||
// Cleanup on logout
|
||||
}
|
||||
```
|
||||
|
||||
## Cron Hooks
|
||||
|
||||
### hook_cron()
|
||||
Perform periodic tasks.
|
||||
```php
|
||||
function mymodule_cron() {
|
||||
// Run periodic maintenance
|
||||
$request_time = \Drupal::time()->getRequestTime();
|
||||
\Drupal::state()->set('mymodule.last_cron', $request_time);
|
||||
}
|
||||
```
|
||||
|
||||
## Installation Hooks
|
||||
|
||||
### hook_install()
|
||||
Perform setup tasks when module is installed.
|
||||
```php
|
||||
function mymodule_install() {
|
||||
// Set default configuration
|
||||
\Drupal::configFactory()->getEditable('mymodule.settings')
|
||||
->set('default_value', 'example')
|
||||
->save();
|
||||
}
|
||||
```
|
||||
|
||||
### hook_uninstall()
|
||||
Clean up when module is uninstalled.
|
||||
```php
|
||||
function mymodule_uninstall() {
|
||||
// Delete configuration and data
|
||||
\Drupal::state()->delete('mymodule.settings');
|
||||
}
|
||||
```
|
||||
|
||||
### hook_schema()
|
||||
Define database tables (in .install file).
|
||||
```php
|
||||
function mymodule_schema() {
|
||||
$schema['mymodule_table'] = [
|
||||
'description' => 'Stores custom data',
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key',
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Name field',
|
||||
],
|
||||
],
|
||||
'primary key' => ['id'],
|
||||
];
|
||||
return $schema;
|
||||
}
|
||||
```
|
||||
|
||||
## Token Hooks
|
||||
|
||||
### hook_tokens()
|
||||
Provide custom tokens.
|
||||
```php
|
||||
function mymodule_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$replacements = [];
|
||||
if ($type == 'node') {
|
||||
foreach ($tokens as $name => $original) {
|
||||
if ($name == 'custom-token') {
|
||||
$replacements[$original] = 'Custom value';
|
||||
}
|
||||
}
|
||||
}
|
||||
return $replacements;
|
||||
}
|
||||
```
|
||||
|
||||
### hook_token_info()
|
||||
Define custom tokens.
|
||||
```php
|
||||
function mymodule_token_info() {
|
||||
$info['tokens']['node']['custom-token'] = [
|
||||
'name' => t('Custom Token'),
|
||||
'description' => t('A custom token for nodes.'),
|
||||
];
|
||||
return $info;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Naming**: Always prefix hooks with your module name
|
||||
2. **Type hints**: Use proper type hints for all parameters
|
||||
3. **Documentation**: Add docblocks explaining what the hook does
|
||||
4. **Performance**: Be mindful of hooks that run frequently (e.g., hook_entity_view)
|
||||
5. **Dependencies**: Inject services via dependency injection in classes, use `\Drupal::service()` in .module files
|
||||
6. **Deprecations**: Check for deprecated hooks when upgrading Drupal versions
|
||||
580
references/module_structure.md
Normal file
580
references/module_structure.md
Normal file
@@ -0,0 +1,580 @@
|
||||
# Drupal Module Structure Reference
|
||||
|
||||
Complete guide to Drupal module structure and development patterns for Drupal 8-11+.
|
||||
|
||||
## Basic Module Structure
|
||||
|
||||
```
|
||||
mymodule/
|
||||
├── mymodule.info.yml # Module metadata (required)
|
||||
├── mymodule.module # Hook implementations
|
||||
├── mymodule.routing.yml # Route definitions
|
||||
├── mymodule.services.yml # Service definitions
|
||||
├── mymodule.permissions.yml # Custom permissions
|
||||
├── mymodule.links.menu.yml # Menu links
|
||||
├── mymodule.links.task.yml # Tab links
|
||||
├── mymodule.links.action.yml # Action links
|
||||
├── composer.json # Composer dependencies
|
||||
├── config/
|
||||
│ ├── install/ # Default configuration
|
||||
│ └── schema/ # Configuration schema
|
||||
│ └── mymodule.schema.yml
|
||||
├── src/
|
||||
│ ├── Controller/ # Controllers
|
||||
│ ├── Form/ # Forms
|
||||
│ ├── Plugin/ # Plugins (Blocks, Fields, etc.)
|
||||
│ │ └── Block/
|
||||
│ ├── Entity/ # Custom entities
|
||||
│ ├── EventSubscriber/ # Event subscribers
|
||||
│ └── Service/ # Custom services
|
||||
├── templates/ # Twig templates
|
||||
└── tests/
|
||||
├── src/
|
||||
│ ├── Unit/ # Unit tests
|
||||
│ ├── Kernel/ # Kernel tests
|
||||
│ └── Functional/ # Functional tests
|
||||
└── modules/ # Test modules
|
||||
```
|
||||
|
||||
## Module Info File (mymodule.info.yml)
|
||||
|
||||
Required metadata for every module:
|
||||
|
||||
```yaml
|
||||
name: My Module
|
||||
description: 'Description of what the module does.'
|
||||
type: module
|
||||
core_version_requirement: ^9 || ^10 || ^11
|
||||
package: Custom
|
||||
|
||||
# Optional dependencies
|
||||
dependencies:
|
||||
- drupal:node
|
||||
- drupal:views
|
||||
- webform:webform
|
||||
|
||||
# Optional configuration dependencies
|
||||
config_devel:
|
||||
install:
|
||||
- core.entity_view_mode.node.teaser
|
||||
optional:
|
||||
- views.view.frontpage
|
||||
|
||||
# Optional PHP requirement
|
||||
php: ^8.1
|
||||
|
||||
# Optional - mark as hidden
|
||||
hidden: true
|
||||
|
||||
# Optional - module version
|
||||
version: 1.0.0
|
||||
```
|
||||
|
||||
## Routing (mymodule.routing.yml)
|
||||
|
||||
Define routes for pages and controllers:
|
||||
|
||||
```yaml
|
||||
# Simple page route
|
||||
mymodule.hello:
|
||||
path: '/hello'
|
||||
defaults:
|
||||
_controller: '\Drupal\mymodule\Controller\HelloController::content'
|
||||
_title: 'Hello World'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
# Route with parameters
|
||||
mymodule.user_page:
|
||||
path: '/user/{user}/custom'
|
||||
defaults:
|
||||
_controller: '\Drupal\mymodule\Controller\UserController::view'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
user: \d+
|
||||
options:
|
||||
parameters:
|
||||
user:
|
||||
type: entity:user
|
||||
|
||||
# Form route
|
||||
mymodule.settings_form:
|
||||
path: '/admin/config/mymodule/settings'
|
||||
defaults:
|
||||
_form: '\Drupal\mymodule\Form\SettingsForm'
|
||||
_title: 'My Module Settings'
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
# Route with custom access
|
||||
mymodule.custom_access:
|
||||
path: '/custom-access'
|
||||
defaults:
|
||||
_controller: '\Drupal\mymodule\Controller\CustomController::content'
|
||||
requirements:
|
||||
_custom_access: '\Drupal\mymodule\Access\CustomAccessCheck::access'
|
||||
```
|
||||
|
||||
## Controllers
|
||||
|
||||
### Basic Controller
|
||||
|
||||
**src/Controller/HelloController.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Drupal\mymodule\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
|
||||
/**
|
||||
* Returns responses for My Module routes.
|
||||
*/
|
||||
class HelloController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Builds the response.
|
||||
*/
|
||||
public function content() {
|
||||
$build['content'] = [
|
||||
'#type' => 'markup',
|
||||
'#markup' => $this->t('Hello World!'),
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Controller with Dependency Injection
|
||||
|
||||
**src/Controller/AdvancedController.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Drupal\mymodule\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Controller with dependency injection.
|
||||
*/
|
||||
class AdvancedController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* Constructs an AdvancedController object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
*/
|
||||
public function __construct(Connection $database) {
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('database')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the response.
|
||||
*/
|
||||
public function content() {
|
||||
// Use injected services
|
||||
$count = $this->database->query('SELECT COUNT(*) FROM {node}')->fetchField();
|
||||
|
||||
$build['content'] = [
|
||||
'#type' => 'markup',
|
||||
'#markup' => $this->t('Total nodes: @count', ['@count' => $count]),
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Forms
|
||||
|
||||
### Configuration Form
|
||||
|
||||
**src/Form/SettingsForm.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Drupal\mymodule\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Configure My Module settings.
|
||||
*/
|
||||
class SettingsForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'mymodule_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['mymodule.settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config('mymodule.settings');
|
||||
|
||||
$form['api_key'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('API Key'),
|
||||
'#default_value' => $config->get('api_key'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['enable_feature'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Enable Feature'),
|
||||
'#default_value' => $config->get('enable_feature'),
|
||||
];
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->config('mymodule.settings')
|
||||
->set('api_key', $form_state->getValue('api_key'))
|
||||
->set('enable_feature', $form_state->getValue('enable_feature'))
|
||||
->save();
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Simple Form
|
||||
|
||||
**src/Form/ContactForm.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Drupal\mymodule\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a contact form.
|
||||
*/
|
||||
class ContactForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'mymodule_contact_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Name'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['email'] = [
|
||||
'#type' => 'email',
|
||||
'#title' => $this->t('Email'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['message'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Message'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Send'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
if (strlen($form_state->getValue('message')) < 10) {
|
||||
$form_state->setErrorByName('message', $this->t('Message is too short.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Process form submission
|
||||
$this->messenger()->addStatus($this->t('Thank you for your message!'));
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Services (mymodule.services.yml)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
mymodule.custom_service:
|
||||
class: Drupal\mymodule\Service\CustomService
|
||||
arguments: ['@entity_type.manager', '@current_user', '@logger.factory']
|
||||
|
||||
mymodule.event_subscriber:
|
||||
class: Drupal\mymodule\EventSubscriber\MyModuleSubscriber
|
||||
arguments: ['@current_user']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
```
|
||||
|
||||
### Custom Service
|
||||
|
||||
**src/Service/CustomService.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Drupal\mymodule\Service;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Session\AccountProxyInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Custom service for My Module.
|
||||
*/
|
||||
class CustomService {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountProxyInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a CustomService object.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, AccountProxyInterface $current_user, LoggerInterface $logger) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->currentUser = $current_user;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a custom operation.
|
||||
*/
|
||||
public function doSomething() {
|
||||
$this->logger->info('Custom service method called by user @uid', [
|
||||
'@uid' => $this->currentUser->id(),
|
||||
]);
|
||||
// Custom logic here
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Plugins
|
||||
|
||||
### Block Plugin
|
||||
|
||||
**src/Plugin/Block/CustomBlock.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Drupal\mymodule\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a custom block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "mymodule_custom_block",
|
||||
* admin_label = @Translation("Custom Block"),
|
||||
* category = @Translation("Custom"),
|
||||
* )
|
||||
*/
|
||||
class CustomBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'custom_text' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$form['custom_text'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Custom Text'),
|
||||
'#default_value' => $this->configuration['custom_text'],
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['custom_text'] = $form_state->getValue('custom_text');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
return [
|
||||
'#markup' => $this->configuration['custom_text'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Permissions (mymodule.permissions.yml)
|
||||
|
||||
```yaml
|
||||
administer mymodule:
|
||||
title: 'Administer My Module'
|
||||
description: 'Configure My Module settings'
|
||||
restrict access: true
|
||||
|
||||
access mymodule content:
|
||||
title: 'Access My Module content'
|
||||
description: 'View content provided by My Module'
|
||||
|
||||
# Dynamic permissions callback
|
||||
permission_callbacks:
|
||||
- Drupal\mymodule\MyModulePermissions::permissions
|
||||
```
|
||||
|
||||
## Menu Links (mymodule.links.menu.yml)
|
||||
|
||||
```yaml
|
||||
mymodule.admin:
|
||||
title: 'My Module'
|
||||
description: 'My Module settings'
|
||||
route_name: mymodule.settings_form
|
||||
parent: system.admin_config
|
||||
weight: 10
|
||||
```
|
||||
|
||||
## Configuration Schema (config/schema/mymodule.schema.yml)
|
||||
|
||||
```yaml
|
||||
mymodule.settings:
|
||||
type: config_object
|
||||
label: 'My Module settings'
|
||||
mapping:
|
||||
api_key:
|
||||
type: string
|
||||
label: 'API Key'
|
||||
enable_feature:
|
||||
type: boolean
|
||||
label: 'Enable Feature'
|
||||
```
|
||||
|
||||
## Event Subscribers
|
||||
|
||||
**src/EventSubscriber/MyModuleSubscriber.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Drupal\mymodule\EventSubscriber;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* My Module event subscriber.
|
||||
*/
|
||||
class MyModuleSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return [
|
||||
KernelEvents::REQUEST => ['onRequest', 0],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Kernel request event handler.
|
||||
*/
|
||||
public function onRequest(RequestEvent $event) {
|
||||
// React to request event
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Namespacing**: Follow PSR-4 autoloading standards
|
||||
2. **Dependency Injection**: Use DI in classes, `\Drupal::service()` in .module files
|
||||
3. **Coding Standards**: Follow Drupal coding standards (use PHPCS)
|
||||
4. **Documentation**: Add comprehensive docblocks
|
||||
5. **Security**: Sanitize output, validate input, check permissions
|
||||
6. **Performance**: Cache when possible, avoid loading unnecessary data
|
||||
7. **Testing**: Write unit, kernel, and functional tests
|
||||
8. **Configuration**: Use config entities for exportable configuration
|
||||
9. **Hooks**: Implement hooks in .module file, not in classes
|
||||
10. **Services**: Create reusable services for business logic
|
||||
Reference in New Issue
Block a user