
drupal-module-polish
by proofoftom
Drupal 10.6+ with Wallet as a Protocol (WaaP) integration - Ethereum wallet authentication using EIP-4361/EIP-191 signatures
SKILL.md
name: "drupal-module-polish" description: "Add configuration management, admin interfaces, code quality tools, and documentation to Drupal custom modules. Use when moving from prototype to production-ready module."
Drupal Module Polish Skill
This skill helps you add production-ready features to Drupal custom modules, following established patterns from the wallet_auth module integration phase.
When to Use This Skill
Use this skill when you need to:
- Add admin settings forms to Drupal modules
- Implement configuration schema and defaults
- Enforce Drupal coding standards (PHPCS, PHPStan)
- Create comprehensive module documentation
- Integrate frontend with backend via drupalSettings
Trigger phrases:
- "add config to drupal module"
- "create admin form"
- "polish drupal module"
- "add drupalsettings"
- "run phpcs"
- "module documentation"
Configuration Management Pattern
1. Create Configuration Schema
File: config/schema/MODULE_NAME.schema.yml
MODULE_NAME.settings:
type: config_object
label: 'Module Name Settings'
mapping:
# String setting
network:
type: string
label: 'Blockchain network'
nullable: true
# Boolean setting
enable_auto_connect:
type: boolean
label: 'Enable auto-connect'
# Integer setting
nonce_lifetime:
type: integer
label: 'Nonce lifetime (seconds)'
# Mapping/nested setting
api_credentials:
type: mapping
label: 'API Credentials'
mapping:
endpoint:
type: uri
label: 'API Endpoint'
key:
type: string
label: 'API Key'
2. Create Default Configuration
File: config/install/MODULE_NAME.settings.yml
# Default configuration values
network: 'mainnet'
enable_auto_connect: true
nonce_lifetime: 300
api_credentials:
endpoint: 'https://api.example.com'
key: ''
3. Create Settings Form
File: src/Form/SettingsForm.php
<?php
namespace Drupal\MODULE_NAME\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure MODULE_NAME settings.
*/
class SettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['MODULE_NAME.settings'];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'MODULE_NAME_settings';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('MODULE_NAME.settings');
// Text field example
$form['network'] = [
'#type' => 'select',
'#title' => $this->t('Blockchain network'),
'#description' => $this->t('Select the blockchain network to use.'),
'#options' => [
'mainnet' => $this->t('Mainnet'),
'sepolia' => $this->t('Sepolia Testnet'),
'polygon' => $this->t('Polygon'),
],
'#default_value' => $config->get('network'),
'#required' => TRUE,
];
// Checkbox example
$form['enable_auto_connect'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable auto-connect'),
'#description' => $this->t('Automatically connect wallet on page load.'),
'#default_value' => $config->get('enable_auto_connect'),
];
// Number field example
$form['nonce_lifetime'] = [
'#type' => 'number',
'#title' => $this->t('Nonce lifetime'),
'#description' => $this->t('How long nonces remain valid (in seconds).'),
'#default_value' => $config->get('nonce_lifetime'),
'#min' => 60,
'#max' => 3600,
'#required' => TRUE,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('MODULE_NAME.settings')
->set('network', $form_state->getValue('network'))
->set('enable_auto_connect', $form_state->getValue('enable_auto_connect'))
->set('nonce_lifetime', $form_state->getValue('nonce_lifetime'))
->save();
parent::submitForm($form, $form_state);
}
}
4. Register Admin Route
File: MODULE_NAME.routing.yml
MODULE_NAME.settings:
path: '/admin/config/people/MODULE_NAME'
defaults:
_form: '\Drupal\MODULE_NAME\Form\SettingsForm'
_title: 'Module Name Settings'
requirements:
_permission: 'administer MODULE_NAME configuration'
5. Add Menu Link
File: MODULE_NAME.links.menu.yml
MODULE_NAME.settings:
title: 'Module Name'
description: 'Configure Module Name settings.'
route_name: MODULE_NAME.settings
parent: user.admin_index # Places under People section
weight: 10
Common parent sections:
user.admin_index- People sectionsystem.admin_config_system- System sectionsystem.admin_config_content- Content sectionsystem.admin_config_services- Services section
6. Add Permission
File: MODULE_NAME.permissions.yml
administer MODULE_NAME configuration:
title: 'Administer Module Name settings'
description: 'Allow users to configure Module Name settings.'
restrict access: true
Code Quality Pattern
PHPCS (PHP Code Sniffer)
Run PHPCS on module:
vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom/MODULE_NAME/
Run PHPCS on specific file:
vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom/MODULE_NAME/src/Controller/ExampleController.php
Auto-fix violations:
vendor/bin/phpcbf --standard=Drupal,DrupalPractice web/modules/custom/MODULE_NAME/
Common PHPCS Violations & Fixes
-
Line length exceeds 80 characters
- Fix: Break long lines, use string concatenation
- Or: Configure to allow 120+ characters
-
Missing namespace doc comment
- Add file-level doc comment after
namespace
- Add file-level doc comment after
-
Unused use statement
- Remove unused
usestatements
- Remove unused
-
Missing array type hint
- Change
array $formtoarray &$form
- Change
PHPStan (Static Analysis)
Run PHPStan:
vendor/bin/phpstan analyse web/modules/custom/MODULE_NAME/ --level=1
With configuration file:
vendor/bin/phpstan analyse -c phpstan.neon web/modules/custom/MODULE_NAME/
Create phpstan.neon:
parameters:
level: 1
paths:
- web/modules/custom/MODULE_NAME/src
bootstrapFiles:
- web/core/tests/bootstrap.php
drupal:
drupalRoot: web
Common PHPStan Errors & Fixes
-
Undefined variable: $config
- Add property declaration:
protected ConfigFactoryInterface $configFactory;
- Add property declaration:
-
Property type not specified
- Add type hints:
private readonly LoggerInterface $logger;
- Add type hints:
-
Unsafe usage of new static()
- Use dependency injection instead
Frontend Integration Pattern
1. Pass Config to Frontend via drupalSettings
In hook_preprocess_page() or in controller:
// In a preprocess hook
function MODULE_NAME_preprocess_page(&$variables) {
$config = \Drupal::config('MODULE_NAME.settings');
$variables['#attached']['drupalSettings']['MODULE_NAME'] = [
'network' => $config->get('network'),
'enableAutoConnect' => $config->get('enable_auto_connect'),
'nonceLifetime' => $config->get('nonce_lifetime'),
];
}
Or in a controller (better pattern):
public function build() {
$config = $this->config('MODULE_NAME.settings');
return [
'#theme' => 'MODULE_NAME_block',
'#attached' => [
'library' => 'MODULE_NAME/wallet',
'drupalSettings' => [
'MODULE_NAME' => [
'network' => $config->get('network'),
'enableAutoConnect' => $config->get('enable_auto_connect'),
'nonceLifetime' => $config->get('nonce_lifetime'),
],
],
],
];
}
2. Access in JavaScript
(function (Drupal, drupalSettings) {
'use strict';
Drupal.behaviors.MODULE_NAME = {
attach: function (context, settings) {
const config = drupalSettings.MODULE_NAME;
console.log('Network:', config.network);
console.log('Auto-connect:', config.enableAutoConnect);
console.log('Nonce lifetime:', config.nonceLifetime);
// Initialize wallet with config
if (config.enableAutoConnect) {
// Auto-connect logic
}
}
};
})(Drupal, drupalSettings);
3. Define Library
File: MODULE_NAME.libraries.yml
wallet:
version: 1.0
js:
js/wallet.js: {}
css:
theme:
css/wallet.css: {}
dependencies:
- core/drupal
- core/drupalSettings
- core/jquery
Documentation Template
README.md Structure
# Module Name
Brief one-line description of what the module does.
## Requirements
- Drupal 10+
- PHP 8.1+
- [Any other dependencies]
## Installation
1. Copy the module to `web/modules/custom/MODULE_NAME`
2. Enable the module: `drush en MODULE_NAME`
3. Or enable via admin interface: Extend > List > Install "Module Name"
4. Configure at `/admin/config/people/MODULE_NAME`
## Configuration
Navigate to **Administration > Configuration > People > Module Name** to configure:
- **Network**: Select the blockchain network (mainnet, sepolia, polygon)
- **Enable auto-connect**: Automatically connect wallet on page load
- **Nonce lifetime**: Set how long nonces remain valid (60-3600 seconds)
## Usage
### For Site Visitors
1. Navigate to a page with the wallet block
2. Click "Connect Wallet"
3. Sign the authentication message in your wallet
4. You are now logged in
### For Developers
#### API Endpoints
**POST /MODULE_NAME/authenticate**
Authenticates a user via wallet signature.
Request:
```json
{
"message": "Domain wants you to sign in...",
"signature": "0x...",
"address": "0x..."
}
Response:
{
"token": "jwt_token_here",
"user": {
"uid": 123,
"name": "user_0x1234..."
}
}
Frontend Integration
Add the wallet block to any region via:
- Structure > Block layout
- Select the desired theme/region
- Place "Wallet Connect" block
Troubleshooting
"Nonce expired or invalid"
Cause: The nonce has exceeded its lifetime or was already used.
Solution:
- Ensure your system clock is synchronized
- Increase the nonce lifetime in module settings
- Clear your browser cache and try again
"Signature verification failed"
Cause: The signature doesn't match the message or address.
Solution:
- Ensure you're signing the exact message displayed
- Check that you're using the correct wallet address
- Try refreshing the page to get a new nonce
"Wallet not connected"
Cause: No wallet extension is detected or connected.
Solution:
- Install a Web3 wallet extension (MetaMask, WalletConnect, etc.)
- Ensure the wallet is unlocked
- Check that your wallet is on the correct network
Configuration not saving
Cause: Missing permissions or cache issues.
Solution:
- Ensure you have "administer MODULE_NAME configuration" permission
- Run
drush crto clear caches - Check watchdog logs for detailed errors
Development Roadmap
- Phase 1: Basic authentication
- Phase 2: REST API
- Phase 3: Frontend integration
- Phase 4: User management
- Phase 5: Configuration & polish
- Phase 6: Testing & validation
Credits
Developed by [Your Name/Organization].
License
GPL-2.0+
## Dependency Injection Pattern
### Inject Configuration Factory
```php
<?php
namespace Drupal\MODULE_NAME\Service;
use Drupal\Core\Config\ConfigFactoryInterface;
use Psr\Log\LoggerInterface;
class MyService {
protected $configFactory;
protected $logger;
/**
* Constructs a new MyService object.
*/
public function __construct(ConfigFactoryInterface $config_factory, LoggerInterface $logger) {
$this->configFactory = $config_factory;
$this->logger = $logger;
}
/**
* Gets configuration value.
*/
public function getSetting($key) {
return $this->configFactory->get('MODULE_NAME.settings')->get($key);
}
}
Define Service
File: MODULE_NAME.services.yml
services:
MODULE_NAME.my_service:
class: Drupal\MODULE_NAME\Service\MyService
arguments:
- '@config.factory'
- '@logger.factory'
Use in Controller/Form
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('MODULE_NAME.my_service')
);
}
public function __construct(MyService $my_service) {
$this->myService = $my_service;
}
Logging Pattern
// Debug: Detailed information for debugging
$this->logger->debug('Nonce generated: @nonce', ['@nonce' => $nonce]);
// Info: Interesting events (login, config changes)
$this->logger->info('User @uid authenticated from wallet @address', [
'@uid' => $account->id(),
'@address' => $address,
]);
// Notice: Normal but significant events
$this->logger->notice('Configuration updated by @user', [
'@user' => $this->currentUser()->getAccountName(),
]);
// Warning: Exception occurrences that aren't errors
$this->logger->warning('Nonce validation failed for address @address', [
'@address' => $address,
]);
// Error: Runtime errors that don't require immediate action
$this->logger->error('Failed to retrieve account info: @message', [
'@message' => $e->getMessage(),
]);
// Critical: Critical conditions
$this->logger->critical('Database connection lost: @error', [
'@error' => $e->getMessage(),
]);
Verification Checklist
After completing module polish:
Configuration
- Schema file created with proper types
- Default config file created
- Settings form extends ConfigFormBase
- All form fields use
$this->t()for labels - Route registered at
/admin/config/... - Menu link appears in correct section
- Permission defined for admin access
- Configuration saves and persists
Code Quality
- PHPCS reports 0 errors
- PHPStan reports 0 errors
- No static
\Drupal::calls (use DI) - All strings use
$this->t() - Proper namespace doc comments
- Type hints on all properties/methods
- PHPDoc comments on public methods
Frontend Integration
- drupalSettings passed to frontend
- Library defined in .libraries.yml
- JavaScript wrapped in Drupal.behaviors
- Config accessible in JS via drupalSettings
Documentation
- README.md exists
- Installation instructions provided
- Configuration documented
- Usage examples included
- Troubleshooting section with common issues
- API endpoints documented (if applicable)
Testing
- Settings form accessible
- Configuration saves correctly
- drupalSettings accessible in browser console
- Caches cleared (
drush cr) - Module enabled/disables cleanly
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon