From 6b9e245063824e227b6214bf6e750ea0b9d366ec Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sun, 30 Nov 2025 08:45:14 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 11 + README.md | 3 + SKILL.md | 463 ++++++++++++++ assets/module-template/MODULENAME.info.yml | 10 + assets/module-template/MODULENAME.module | 22 + assets/module-template/MODULENAME.routing.yml | 8 + assets/module-template/README.md | 36 ++ .../src/Controller/ExampleController.php | 24 + plugin.lock.json | 73 +++ references/hooks.md | 317 ++++++++++ references/module_structure.md | 580 ++++++++++++++++++ 11 files changed, 1547 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 SKILL.md create mode 100644 assets/module-template/MODULENAME.info.yml create mode 100644 assets/module-template/MODULENAME.module create mode 100644 assets/module-template/MODULENAME.routing.yml create mode 100644 assets/module-template/README.md create mode 100644 assets/module-template/src/Controller/ExampleController.php create mode 100644 plugin.lock.json create mode 100644 references/hooks.md create mode 100644 references/module_structure.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..94e92ba --- /dev/null +++ b/.claude-plugin/plugin.json @@ -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": [ + "./" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..75cb77d --- /dev/null +++ b/README.md @@ -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. diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..78ec5d4 --- /dev/null +++ b/SKILL.md @@ -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 diff --git a/assets/module-template/MODULENAME.info.yml b/assets/module-template/MODULENAME.info.yml new file mode 100644 index 0000000..627957a --- /dev/null +++ b/assets/module-template/MODULENAME.info.yml @@ -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 diff --git a/assets/module-template/MODULENAME.module b/assets/module-template/MODULENAME.module new file mode 100644 index 0000000..aa31ca8 --- /dev/null +++ b/assets/module-template/MODULENAME.module @@ -0,0 +1,22 @@ +' . t('About') . ''; + $output .= '

' . t('Description of what this module does.') . '

'; + return $output; + + default: + } +} diff --git a/assets/module-template/MODULENAME.routing.yml b/assets/module-template/MODULENAME.routing.yml new file mode 100644 index 0000000..e3b7be0 --- /dev/null +++ b/assets/module-template/MODULENAME.routing.yml @@ -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' diff --git a/assets/module-template/README.md b/assets/module-template/README.md new file mode 100644 index 0000000..57d5fea --- /dev/null +++ b/assets/module-template/README.md @@ -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` diff --git a/assets/module-template/src/Controller/ExampleController.php b/assets/module-template/src/Controller/ExampleController.php new file mode 100644 index 0000000..69c636c --- /dev/null +++ b/assets/module-template/src/Controller/ExampleController.php @@ -0,0 +1,24 @@ + 'markup', + '#markup' => $this->t('Example content from MODULELABEL module.'), + ]; + + return $build; + } + +} diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..d2d0826 --- /dev/null +++ b/plugin.lock.json @@ -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": [] + } +} \ No newline at end of file diff --git a/references/hooks.md b/references/hooks.md new file mode 100644 index 0000000..ba20643 --- /dev/null +++ b/references/hooks.md @@ -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 diff --git a/references/module_structure.md b/references/module_structure.md new file mode 100644 index 0000000..5239070 --- /dev/null +++ b/references/module_structure.md @@ -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 + 'markup', + '#markup' => $this->t('Hello World!'), + ]; + return $build; + } + +} +``` + +### Controller with Dependency Injection + +**src/Controller/AdvancedController.php** +```php +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 +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 + '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 +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 + '', + ]; + } + + /** + * {@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 + ['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